﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Cci;

using PartialExpressionCompleter.Util;
using Microsoft.Cci.Immutable;
using PartialExpressionCompleter.Experiment;

namespace PartialExpressionCompleter.Query
{
    abstract class Expression
    {
        /// Cost for a bare constant expression
        public const int CONSTANT_COST = 2;

        public Expression()
        {
            BaseCost = 0;
        }

        /// <summary>
        /// Is this a complete valid expression in the programming language.
        /// If true, then this.IsNonTrivialQuery() is false.
        /// </summary>
        public abstract bool IsComplete { get; }
        /// <summary>
        /// Could this query possibly return a result other than itself.
        /// Different from this.IsComplete() only because UnknownExpression ("?")
        /// is not a complete expression.
        /// </summary>
        public abstract bool IsNonTrivialQuery { get; }
        /// <summary>
        /// True if this part of the expression was provided by the user
        /// (as opposed to being generated while searching for results).
        /// </summary>
        public abstract bool IsUserProvided { get; }
        /// <summary>
        /// True if this can be assigned to.
        /// </summary>
        public virtual bool IsLValue { get { return false;  } }

        // TODO Should this always be first?
        // TODO This probably has to guarentee increasing scores.
        protected abstract IEnumerable<Expression> GetRewritings();
        // TODO ... as does this.
        protected abstract IEnumerable<Expression> GetExactQueryResults();
        public virtual IEnumerable<Expression> GetUnorderedQueryResults<E>(QueryContext<E> c) where E : IExpression
        {
            // TODO This probably doesn't actually work... ?
            return this.GetExactQueryResults()
                       .Concat(from query in this.GetRewritings()
                              from res in query.GetExactQueryResults()
                              select res)
                       .Distinct();
        }
        public IEnumerable<Expression> GetQueryResults<E>(QueryContext<E> c) where E : IExpression
        {
            return GetUnorderedQueryResults(c).OrderBy(e => e.Score(c));
        }

        public abstract ITypeDefinition Type { get; }

        public virtual int Score<E>(QueryContext<E> c) where E : IExpression { return 1000; }

        public abstract override string ToString();
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is Expression
                && obj.ToString().Equals(this.ToString());
        }

        public HashSet<PartialExpressionCompleter.Experiment.TypeTerm> Meanings { get; set; }
        public int BaseCost { get; set; }
        public ITypeDefinitionMember TypeMember { get; set; }
    }

    abstract class CompleteExpression : Expression
    {
        public override bool IsComplete { get { return true; } }
        public override bool IsNonTrivialQuery { get { return false; } }

        protected override IEnumerable<Expression> GetRewritings()
        {
            return Enumerable.Empty<Expression>();
        }

        protected override IEnumerable<Expression> GetExactQueryResults()
        {
            yield return this;
        }

        public override bool IsUserProvided
        {
            get { return true; }
        }
    }
    abstract class BaseCompleteExpression : CompleteExpression
    {
        private readonly string display;
        private readonly ITypeDefinition type;

        public BaseCompleteExpression(ITypeDefinition type, string display)
        {
            this.type = type;
            this.display = display;
        }

        public override string ToString()
        {
            return display;
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class UnsupportedCompleteExpression : BaseCompleteExpression
    {
        private readonly IExpression exp;
        private static int id = 1;

        public UnsupportedCompleteExpression(IExpression exp, ITypeReference type)
            : base(type.ResolvedType,
                   "(" + exp.GetType() + "-unknown" + (id++) + ")")
        {
            this.exp = exp;
        }
        public UnsupportedCompleteExpression(IExpression exp)
            : this(exp, exp.Type)
        { }
    }

    class CompileTimeConstant : CompleteExpression
    {
        private readonly ITypeDefinition type;
        private readonly object value;

        public CompileTimeConstant(ITypeDefinition type, object value)
        {
            this.type = type;
            this.value = value;
        }

        public override ITypeDefinition Type
        {
            get { return type; }
        }

        public override string ToString()
        {
            return value == null ? "null" : value.ToString();
        }
    }
    class StringCompileTimeConstant : CompileTimeConstant
    {
        public StringCompileTimeConstant(ITypeDefinition type, string value)
            : base(type, value) 
        { }

        public override string ToString()
        {
            return "\"" + base.ToString() + "\"";
        }
    }
    class CreateDelegate : CompleteExpression
    {
        private readonly ITypeDefinition type;
        private readonly Expression instance;
        private readonly IMethodReference meth;

        public CreateDelegate(ITypeDefinition type,
                              Expression inst,
                              IMethodReference meth)
        {
            this.type = type;
            this.instance = inst;
            this.meth = meth;
        }

        public override string ToString()
        {
            return "DELEGATE(" + (instance != null && !instance.ToString().Equals("null")
                    ? "this=" + instance.ToString() + ", " : "")
                + meth.ToString() + ")";
        }

        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class Closure : CompleteExpression
    {
        private readonly ITypeDefinition type;

        public Closure(IAnonymousDelegate del)
        {
            this.type = del.Type.ResolvedType;
        }

        public override string ToString()
        {
            return "CLOSURE(type=" + type + ")";
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class ArrayLookup : CompleteExpression
    {
        private readonly Expression arr;
        private readonly List<Expression> i;
        private readonly ITypeDefinition type;

        private ArrayLookup(Expression arr, List<Expression> i, ITypeDefinition type)
        {
            this.arr = arr;
            this.i = i;
            this.type = type;
        }

        public static ArrayLookup Create<E>(IArrayIndexer ai,
                                                       QueryContext<E> context) where E : IExpression
        {
            var arr = ai.IndexedObject.AsExpression(context);
            var ii = ai.Indices.Select(i => i.AsExpression(context)).ToList();
            var type = ai.Type.ResolvedType;

            return new ArrayLookup(arr, ii, type);
        }

        public override string ToString()
        {
            return arr + "[" + i.CommaSeparated() + "]";
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }

        public override bool IsLValue
        {
            get { return this.arr.IsLValue; }
        }
    }
    class CreateArray : CompleteExpression
    {
        private readonly List<Expression> sizes;
        private readonly ITypeDefinition elType, type;

        private CreateArray(List<Expression> sizes, ITypeDefinition elType,
                           ITypeDefinition type)
        {
            this.sizes = sizes;
            this.elType = elType;
            this.type = type;
        }

        public static CreateArray Create<E>(ICreateArray ca, QueryContext<E> context) where E : IExpression
        {
            var sizes = ca.Sizes.Select(i => i.AsExpression(context)).ToList();
            var elType = ca.ElementType.ResolvedType;
            var type = ca.Type.ResolvedType;

            return new CreateArray(sizes, elType, type);
        }

        public override string ToString()
        {
            return "new " + elType
                + string.Concat(sizes.Select(size => "[" + size + "]").ToArray());
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class AddressOf : CompleteExpression
    {
        private readonly Expression source;
        private readonly ITypeDefinition type;

        public AddressOf(ITypeDefinition type, Expression source)
        {
            this.source = source;
            this.type = type;
        }
        public static AddressOf Create<E>(IAddressOf ao, QueryContext<E> context) where E : IExpression
        {
            return new AddressOf(ao.Type.ResolvedType, ao.Expression.AsExpression(context));
        }

        public Expression Source { get { return source; } }
        public override string ToString()
        {
            return "(out " + source + ")";
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class TypeOf : CompleteExpression
    {
        private readonly ITypeOf typeOf;
        public TypeOf(ITypeOf typeOf)
        {
            this.typeOf = typeOf;
        }

        public override string ToString()
        {
            return "typeof(" + typeOf.TypeToGet + ")";
        }
        public override ITypeDefinition Type
        {
            get { return typeOf.Type.ResolvedType; }
        }
    }

    class LocalVariable : CompleteExpression
    {
        private readonly ILocalDefinition local;
        private string name;
        private ITypeDefinition type;
        public LocalVariable(ILocalDefinition local)
        {
            this.local = local;
            this.name = local.ToString();
            this.type = local.Type.ResolvedType;
        }

        public LocalVariable(string name, ITypeDefinition type)
        {
            this.name = name;
            this.type = type;
        }

        public override string ToString()
        {
            return this.name;
        }

        public override ITypeDefinition Type
        {
            get { return this.type; }
        }

        public override bool IsLValue
        {
            get { return !local.IsConstant; }
        }
    }
    class ParamVariable : CompleteExpression
    {
        private readonly IParameterDefinition p;
        public ParamVariable(IParameterDefinition p)
        {
            this.p = p;
        }

        public override string ToString()
        {
            return p.Name.ToString();
        }

        public override ITypeDefinition Type
        {
            get { return p.Type.ResolvedType; }
        }

        public override bool IsLValue
        {
            get { return true; }
        }
    }
    class This : CompleteExpression
    {
        private readonly ITypeDefinition type;
        public This(ITypeDefinition type)
        {
            this.type = type;
        }

        public override string ToString()
        {
            return "this";
        }

        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class Cast : CompleteExpression
    {
        private readonly ITypeDefinition type;
        private readonly Expression source;

        public Cast(ITypeDefinition type, Expression source)
        {
            this.type = type;
            this.source = source;
        }

        public override string ToString()
        {
            return "(" + type + ") " + source;
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class BinaryOperation : CompleteExpression
    {
        private readonly Expression left, right;
        private readonly string op;
        private readonly ITypeDefinition type;

        public BinaryOperation(Expression left, Expression right, string op, ITypeDefinition type)
        {
            this.left = left;
            this.right = right;
            this.op = op;
            this.type = type;
        }

        public Expression Left { get { return left; } }
        public Expression Right { get { return right; } }

        public override ITypeDefinition Type
        {
            get { return type; }
        }

        public override string ToString()
        {
            return "(" + left + " " + op + " " + right + ")";
        }
    }
    class UnaryOperation : CompleteExpression
    {
        private readonly Expression operand;
        private readonly string op;
        private readonly ITypeDefinition type;

        public UnaryOperation(Expression operand, string op, ITypeDefinition type)
        {
            this.operand = operand;
            this.op = op;
            this.type = type;
        }

        public override ITypeDefinition Type
        {
            get { return type; }
        }

        public override string ToString()
        {
            return op + "(" + operand + ")";
        }
    }
    class Conditional : CompleteExpression
    {
        private readonly Expression condition, resTrue, resFalse;
        private readonly ITypeDefinition type;

        public Conditional(Expression condition,
                           Expression resTrue, Expression resFalse,
                           ITypeDefinition type)
        {
            this.condition = condition;
            this.resTrue = resTrue;
            this.resFalse = resFalse;
            this.type = type;
        }

        public override string ToString()
        {
            return "(" + condition + " ? " + resTrue + " : " + resFalse + ")";
        }
        public override ITypeDefinition Type
        {
            get { return type; }
        }
    }
    class FieldLookup : CompleteExpression
    {
        private readonly Expression receiver;
        private readonly IFieldDefinition field;
        public FieldLookup(Expression receiver, IFieldDefinition field)
        {
            if (receiver is AddressOf)
            {
                this.receiver = (receiver as AddressOf).Source;
            }
            else
            {
                this.receiver = receiver;
            }
            this.field = field;
            this.TypeMember = field;
        }

        public Expression Receiver { get { return receiver; } }
        public override ITypeDefinition Type
        {
            get { return field.Type.ResolvedType; }
        }
        public string FieldName { get { return field.Name.ToString(); } }

        public override string ToString()
        {
            return receiver.ToString() + "." + field.Name.ToString();
        }

        public override bool IsLValue
        {
            get { return !field.IsReadOnly; }
        }
    }
    class StaticFieldLookup : CompleteExpression
    {
        private readonly IFieldDefinition field;
        public StaticFieldLookup(IFieldDefinition field)
        {
            this.field = field;
            this.TypeMember = field;
        }

        public override ITypeDefinition Type
        {
            get { return field.Type.ResolvedType; }
        }

        public override string ToString()
        {
            return field.ToString();
        }

        public override bool IsLValue
        {
            get { return !field.IsReadOnly; }
        }
    }
    class PropertyLookup : CompleteExpression
    {
        private readonly Expression receiver;
        private readonly string prop;
        private readonly ITypeDefinition type;
        private readonly bool isLValue;
        public PropertyLookup(Expression receiver, ITypeDefinition type, string prop, bool lvalue)
        {
            this.receiver = receiver;
            this.type = type;
            this.prop = prop;
            this.isLValue = lvalue;
        }

        public PropertyLookup(Expression reciever, IPropertyDefinition pd)
            : this(reciever, pd.Type.ResolvedType, pd.Name.ToString(), pd.Setter != null)
        {
            this.TypeMember = pd;
        }

        public Expression Receiver { get { return receiver; } }
        public override ITypeDefinition Type
        {
            get { return type.ResolvedType; }
        }
        public string PropertyName { get { return prop; } }

        public override string ToString()
        {
            return receiver.ToString() + "." + prop;
        }

        public override bool IsLValue
        {
            get { return isLValue; }
        }
    }
    class StaticPropertyLookup : CompleteExpression
    {
        private readonly ITypeDefinition reciever;
        private readonly string prop;
        private readonly ITypeDefinition type;
        private readonly bool isLValue;
        public StaticPropertyLookup(ITypeDefinition reciever, ITypeDefinition type, string prop, bool lvalue)
        {
            this.reciever = reciever;
            this.type = type;
            this.prop = prop;
            this.isLValue = lvalue;
        }

        public StaticPropertyLookup(IPropertyDefinition pd)
            : this(pd.ContainingTypeDefinition, pd.Type.ResolvedType, pd.Name.ToString(), pd.Setter != null)
        {
            this.TypeMember = pd;
        }

        public override ITypeDefinition Type
        {
            get { return type.ResolvedType; }
        }

        public override string ToString()
        {
            return reciever.ToString() + "." + prop;
        }

        public override bool IsLValue
        {
            get { return isLValue; }
        }
    }

    class UnknownExpression : Expression
    {
        private static readonly UnknownExpression instance = new UnknownExpression();
        private UnknownExpression() { }
        public static UnknownExpression Instance { get { return instance; } }

        public override string ToString()
        {
            return "?";
        }

        public override bool IsComplete { get { return false; } }
        public override bool IsNonTrivialQuery { get { return false; } }

        protected override IEnumerable<Expression> GetRewritings()
        {
            return Enumerable.Empty<Expression>();
        }

        protected override IEnumerable<Expression> GetExactQueryResults()
        {
            yield return this;
        }

        public override ITypeDefinition Type
        {
            get { return null; }
        }

        public override bool IsUserProvided
        {
            get { return false; }
        }
    }
}
